#include <mathematics/AABB.h>
#include <mathematics/Plane.h>
#include <mathematics/Sphere.h>

namespace ne
{
    AABB::AABB()
        : minimum(Vector3::ZERO),
          maximum(Vector3::ZERO),
          mExtent(AABB_EXTENT_NULL),
          mpCorners(0)
    {
    }

    AABB::AABB(const AABB &a)
        : minimum(a.minimum),
          maximum(a.maximum),
          mExtent(a.mExtent),
          mpCorners(0)
    {
    }

    AABB::AABB(const Vector3 &minPoint, const Vector3 &maxPoint)
        : minimum(minPoint),
          maximum(maxPoint),
          mExtent(AABB_EXTENT_FINITE),
          mpCorners(0)
    {
        redefine(minPoint, maxPoint);
    }

    void AABB::redefine(const Vector3 &minPoint, const Vector3 &maxPoint)
    {
        assert(minPoint.x <= maxPoint.x &&
               minPoint.y <= maxPoint.y &&
               minPoint.z <= maxPoint.z);

        minimum = minPoint;
        maximum = maxPoint;
        mExtent = AABB_EXTENT_FINITE;
    }

    void AABB::setExtent(const AABBExtent e)
    {
        mExtent = e;
    }

    AABBExtent AABB::getExtent() const
    {
        return mExtent;
    }

    bool AABB::isNull() const
    {
        return (mExtent == AABB_EXTENT_NULL);
    }

    bool AABB::isFinite() const
    {
        return (mExtent == AABB_EXTENT_FINITE);
    }

    bool AABB::isInfinite() const
    {
        return (mExtent == AABB_EXTENT_INFINITE);
    }

    Vector3 AABB::getCenter() const
    {
        assert(isFinite());
        return Vector3::Mid(minimum, maximum);
    }

    Vector3 AABB::getSize() const
    {
        switch (mExtent)
        {
            case AABB_EXTENT_NULL:
            {
                return Vector3::ZERO;
            }

            case AABB_EXTENT_FINITE:
            {
                return maximum - minimum;
            }

            case AABB_EXTENT_INFINITE:
            {
                return Vector3(Math::POS_HUGE_VAL,
                               Math::POS_HUGE_VAL,
                               Math::POS_HUGE_VAL);
            }

            default: return Vector3::ZERO;
        }
    }

    Vector3 AABB::getHalfSize() const
    {
        switch (mExtent)
        {
            case AABB_EXTENT_NULL:
            {
                return Vector3::ZERO;
            }

            case AABB_EXTENT_FINITE:
            {
                return (maximum - minimum) * 0.5f;
            }

            case AABB_EXTENT_INFINITE:
            {
                return Vector3(Math::POS_HUGE_VAL,
                               Math::POS_HUGE_VAL,
                               Math::POS_HUGE_VAL);
            }

            default: return Vector3::ZERO;
        }
    }

    const Vector3* AABB::getCorners() const
    {
        assert(isFinite());

        if (!mpCorners)
        {
            mpCorners = new Vector3[8];
        }
        mpCorners[FAR_LEFT_BOTTOM] = minimum;
        mpCorners[FAR_LEFT_TOP] = Vector3(minimum.x, maximum.y, minimum.z);
        mpCorners[FAR_RIGHT_TOP] = Vector3(maximum.x, maximum.y, minimum.z);
        mpCorners[FAR_RIGHT_BOTTOM] = Vector3(maximum.x, minimum.y, minimum.z);
        mpCorners[NEAR_RIGHT_BOTTOM] = Vector3(maximum.x, minimum.y, maximum.z);
        mpCorners[NEAR_LEFT_BOTTOM] = Vector3(minimum.x, minimum.y, maximum.z);
        mpCorners[NEAR_LEFT_TOP] = Vector3(minimum.x, maximum.y, maximum.z);
        mpCorners[NEAR_RIGHT_TOP] = maximum;
        return mpCorners;
    }

    AABB& AABB::operator = (const AABB &a)
    {
        minimum = a.minimum;
        maximum = a.maximum;
        mExtent = a.mExtent;
        return *this;
    }

    bool AABB::operator == (const AABB &a) const
    {
        if (mExtent != a.mExtent) return false;
        if (!isFinite()) return true;
        return (minimum == a.minimum) && (maximum == a.maximum);
    }

    bool AABB::operator != (const AABB &a) const
    {
        return !(*this == a);
    }

    void AABB::merge(const AABB &a)
    {
        if (isInfinite() || a.isNull())
        {
            return;
        }
        else if (a.isInfinite())
        {
            mExtent = AABB_EXTENT_INFINITE;
        }
        else
        {
            Vector3 minPoint = minimum;
            Vector3 maxPoint = maximum;
            minPoint.makeFloor(a.minimum);
            maxPoint.makeCeil(a.maximum);
            redefine(minPoint, maxPoint);
        }
    }

    void AABB::merge(const Vector3 &v)
    {
        switch (mExtent)
        {
            case AABB_EXTENT_NULL:
            {
                redefine(v, v);
                return;
            }

            case AABB_EXTENT_FINITE:
            {
                Vector3 minPoint = minimum;
                Vector3 maxPoint = maximum;
                minPoint.makeFloor(v);
                maxPoint.makeCeil(v);
                redefine(minPoint, maxPoint);
                return;
            }

            case AABB_EXTENT_INFINITE:
            {
                return;
            }

            default: return;
        }
    }

    AABB AABB::intersection(const AABB &a) const
    {
        if (isNull() || a.isNull())
        {
            return AABB();
        }
        else if (isInfinite())
        {
            return AABB(a);
        }
        else if (a.isInfinite())
        {
            return AABB(*this);
        }

        Vector3 minPoint = minimum;
        Vector3 maxPoint = maximum;
        minPoint.makeCeil(a.minimum);
        maxPoint.makeFloor(a.maximum);
        if (minPoint < maxPoint)
        {
            return AABB(minPoint, maxPoint);
        }

        return AABB();
    }

    bool AABB::intersects(const AABB &a) const
    {
        if (isNull() || a.isNull()) return false;

        if (isInfinite() || a.isInfinite()) return true;

        if (maximum.x < a.minimum.x) return false;
        if (maximum.y < a.minimum.y) return false;
        if (maximum.z < a.minimum.z) return false;

        if (minimum.x > a.maximum.x) return false;
        if (minimum.y > a.maximum.y) return false;
        if (minimum.z > a.maximum.z) return false;

        return true;
    }

    bool AABB::intersects(const Sphere &s) const
    {
        return s.intersects(*this);
    }

    bool AABB::intersects(const Plane &p) const
    {
        if (isNull()) return false;
        if (isInfinite()) return true;

        return (p.getSide(*this) == Plane::BOTH_SIDE);
    }

    bool AABB::intersects(const Vector3 &v) const
    {
        switch (mExtent)
        {
            case AABB_EXTENT_NULL:
            {
                return false;
            }

            case AABB_EXTENT_FINITE:
            {
                return (minimum.x <= v.x && v.x <= maximum.x &&
                        minimum.y <= v.y && v.y <= maximum.y &&
                        minimum.z <= v.z && v.z <= maximum.z);
            }

            case AABB_EXTENT_INFINITE:
            {
                return true;
            }

            default: return false;
        }
    }

    std::ostream& operator << (std::ostream &o, const AABB &a)
    {
        o << "AABB(minimum=" << a.minimum <<
                ", maximum=" << a.maximum << ")";
        return o;
    }

    AABB::~AABB()
    {
        if (mpCorners)
        {
            delete[] mpCorners;
        }
    }
}
